/**
 * Copyright 2012 Alfredo "Rainbowbreeze" Morresi
 * 
 * This file is part of Apps4Italy PM10 project.
 * 
 * PM10 is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * PM10 is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with Foobar. If not, see http://www.gnu.org/licenses/.
 */
package it.rainbowbreeze.eureka.data;

import static it.rainbowbreeze.libs.common.RainbowContractHelper.checkNotNull;
import it.rainbowbreeze.eureka.common.AppEnv;
import it.rainbowbreeze.eureka.common.LogFacility;
import it.rainbowbreeze.eureka.common.ResultOperation;
import it.rainbowbreeze.eureka.domain.QualityDataPoint;
import it.rainbowbreeze.eureka.logic.JsonConverter;
import it.rainbowbreeze.libs.helper.RainbowArrayHelper;
import it.rainbowbreeze.libs.net.IRainbowRestfulClient.ServerResponse;

import java.io.IOException;
import java.net.HttpURLConnection;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;

/**
 * This class is responsible for BetterLife webservices communication
 * 
 * @author Alfredo "Rainbowbreeze" Morresi
 */
public class WebserviceManager {
    // ------------------------------------------ Private Fields
    private static final String LOG_HASH = "WebserviceManager";
    
    private final LogFacility mLogFacility;
    private final RestfulClient mRestfulClient;
    private final JsonConverter mJsonConverter;
    
    
    // -------------------------------------------- Constructors
    public WebserviceManager(LogFacility logFacility,
            RestfulClient restfulClient,
            JsonConverter jsonConverter) {
        mLogFacility = checkNotNull(logFacility, "LogFacility");
        mRestfulClient = checkNotNull(restfulClient, "RestfulClient");
        mJsonConverter = checkNotNull(jsonConverter, "JsonConverter");
    }

    
    // --------------------------------------- Public Properties
    private String mBaseServerUrl = AppEnv.BASE_SERVER_URL;
    protected String getBaseServerUrl()
    { return mBaseServerUrl; }
    
    // ------------------------------------------ Public Methods
    
    /**
     * 
     * @param latitude latitude center in microdegrees
     * @param longitude longitude center in microdegrees
     * @param radius in meters
     * @return
     */
    public ResultOperation<QualityDataPoint[]> getDataPoints(
            int latitude,
            int longitude,
            int radius,
            int[] measureIds) {
        mLogFacility.v(LOG_HASH, "Getting quality points for latitude " + latitude + " and longitude " + longitude);
        
        StringBuilder urlBuilder = new StringBuilder();
        urlBuilder.append(getBaseServerUrl())
            //TODO use geoscore api
            .append("geo/")
            .append(latitude/1E6)
            .append("/")
            .append(longitude/1E6)
            .append("?radius=")
            .append(radius/1000)
            .append("&year=2009"); //TODO Fake
        if (null != measureIds) {
            String measureIdsKey = RainbowArrayHelper.join(measureIds, ",");
            urlBuilder.append("&type=")
            .append(measureIdsKey);
        }

        ServerResponse serverResponse;
        try {
            serverResponse = mRestfulClient.requestGet(urlBuilder.toString());
        } catch (IOException e) {
            return new ResultOperation(e, ResultOperation.RETURNCODE_ERROR_COMMUNICATION);
        }
        
        if (hasError(serverResponse)) {
            return processError(serverResponse, QualityDataPoint[].class);
        }

        String jsonResponse = serverResponse.getResponseMessage();
        mLogFacility.v(LOG_HASH, jsonResponse);

        QualityDataPoint[] serverData;
        try {
            serverData = mJsonConverter.fromServerDataPointsInfo(jsonResponse);
        } catch (Exception e) {
            mLogFacility.e(LOG_HASH, e);
            return new ResultOperation<QualityDataPoint[]>(e, ResultOperation.RETURNCODE_ERROR_JSON_SERIALIZING);
        }
        
        ResultOperation<QualityDataPoint[]> result = new ResultOperation<QualityDataPoint[]>(serverData);
        return result;
    }
    
//    public ResultOperation<Long[]> searchUser(long userId, String textToSearch) {
//        mLogFacility.v(LOG_HASH, "Searching for user with text " + textToSearch);
//        
//        /**
//         * From server's doc
//         * userId the ID of the user whose contacts should not taken into account, if requested
//         * text pattern to be matched
//         * returnSameUser if false, don't take into account the user specified by userId
//         * returnFriendsOfUser if false, don't take into account the contacts of the user specified by userId
//         */
//        HashMap<String, Object> input = new HashMap<String,Object>();
//        input.put("userID", userId);
//        input.put("text", textToSearch);
//        input.put("returnSameUser", false);
//        input.put("returnFriendsOfUser", false);
//        
//        ResultOperation<Long[]> result = prepareAndSendRequest("searchUser", input, Long[].class);
//        if (result.hasErrors()) {
//            mLogFacility.e(LOG_HASH, "Cannot search for user. Cause: " + result.getReturnCode());
//        }
//        return result;
//        
//    }
    
    // ----------------------------------------- Private Methods
    private String combineUrl(String serverUrl, String serviceAddress) {
        return serverUrl.endsWith("/")
                ? serverUrl + serviceAddress
                : serverUrl + "/" + serviceAddress;
    }
    
//    private <ResultType> ResultOperation<ResultType> prepareAndSendRequest(
//            String action,
//            Object input,
//            Class<ResultType> classType) {
//        
//        ServerResponse serverResponse;
//        try {
//            serverResponse = sendReq(action, input);
//        } catch (JsonGenerationException e) {
//            return new ResultOperation<ResultType>(e, ResultOperation.RETURNCODE_ERROR_APPLICATION_ARCHITECTURE);
//        } catch (JsonMappingException e) {
//            return new ResultOperation<ResultType>(e, ResultOperation.RETURNCODE_ERROR_APPLICATION_ARCHITECTURE);
//        } catch (IOException e) {
//            return new ResultOperation<ResultType>(e, ResultOperation.RETURNCODE_ERROR_COMMUNICATION);
//        }
//
//        if (hasError(serverResponse)) {
//            return processError(serverResponse, classType);
//        }
//
//        String jsonResponse = serverResponse.getMessageBody();
//
//        ResultType returnObject = null;
//        try {
//            returnObject = mJsonConverter.fromJson(jsonResponse, classType);
//        } catch (Exception e) {
//            mLogFacility.e(LOG_HASH, e);
//            return new ResultOperation(e, ResultOperation.RETURNCODE_ERROR_JSON_SERIALIZING);
//        }
//        
////        if (null == returnObject) return ResultOperation.JSON_DESERIALIZING_ERROR;
//
//        ResultOperation<ResultType> result = new ResultOperation<ResultType>();
//        result.setResult(returnObject);
//        return result;
//    }
    
    
    /**
     * Executes a call to webservice
     * 
     * @param action webservice call
     * @param input data to pass to webservice (will be converted in JSON)

     * @return
     * @throws IOException 
     * @throws JsonMappingException 
     * @throws JsonGenerationException 
     */
    private ServerResponse sendReq(String action, Object input)
    throws JsonGenerationException, JsonMappingException, IOException {
        String inputData = "";
        ServerResponse serverResponse = null;
        
        String endp = combineUrl(getBaseServerUrl(), action);
        inputData = mJsonConverter.toJson(input);
        serverResponse = mRestfulClient.postDataToWebservice(endp, inputData);

        return serverResponse;
    }

    /**
     * Verifies if the webservice response contains errors
     * 
     * @param serverResponse
     * @return
     */
    protected boolean hasError(ServerResponse serverResponse) {
        if (null == serverResponse) return true;
        
        //first of all, checks server response code
        if (serverResponse.getResponseCode() != HttpURLConnection.HTTP_OK)
            return true;
        
        //then checks body format
//        long webserviceReturnCode = mJsonConverter.getWebserviceErrorCode(serverResponse.getMessageBody());
//        if (webserviceReturnCode < 0) return true;
        
        return false;
    }
    
    /**
     * Reads from webservice response the error and create appropriate object
     * 
     * @param <ResultType> the generic for {@link ResultOperation}
     * @param serverResponse the webservice response
     * @param resultType the generic for {@link ResultOperation}
     * 
     * @return a new {@link ResultOperation} object with standard error code
     */
    private <ResultType> ResultOperation<ResultType> processError(
            ServerResponse serverResponse,
            Class<ResultType> resultType) {

        int errorReturnCode;
        Exception exception = null;
        
        //TODO manage specific error codes

        if (null == serverResponse) {
            //http error in incapsulated in a generic communication error
            errorReturnCode = ResultOperation.RETURNCODE_ERROR_COMMUNICATION;

            //value of http error is put inside the exception
            exception = new Exception("HTTP Error " + serverResponse.getResponseCode());
            
        } else if (serverResponse.getResponseCode() != HttpURLConnection.HTTP_OK) {
            //http error in incapsulated in a generic communication error
            errorReturnCode = ResultOperation.RETURNCODE_ERROR_COMMUNICATION;
            //value of http error is put inside the exception
            exception = new Exception("HTTP Error " + serverResponse.getResponseCode());
            mLogFacility.v(LOG_HASH, "Network operation returns an error code " + serverResponse.getResponseCode());
        } else {
            errorReturnCode = (int) mJsonConverter.getWebserviceErrorCode(serverResponse.getMessageBody());
            mLogFacility.v(LOG_HASH, "Webservice returns an error code " + errorReturnCode);
        }

        ResultOperation<ResultType> result = new ResultOperation<ResultType>(exception, errorReturnCode);
        
        return result;
    }
}
